// https://blog.csdn.net/rxbook/article/details/130646031

package main

import (
	"fmt"
	"unsafe"
)

type (
	MyInt     int
	YourInt   MyInt
	MyString  string
	mapIntStr map[int]string
	sliStr    []string
)
type IntNew int    //基于int类型定义了一个新的类型:IntNew
type IntSelf = int //对int类型起了个别名:IntSelf

func main() {
	var n1 MyInt
	var n2 YourInt = 5
	n1 = MyInt(n2) // ok
	fmt.Println(n1)
	var s MyString = "hello"
	//n1 = MyInt(s) // 错误：cannot convert s (variable of type MyString) to type MyInt
	fmt.Println(s)

	//定义新类型 和 类型别名 的区别
	var n3 IntNew
	var n4 IntSelf
	fmt.Printf("n3: %v, %T \n", n3, n3) //n3: 0, main.IntNew
	fmt.Printf("n4: %v, %T \n", n4, n4) //n4: 0, int

	fmt.Println("-------------")

	StructDemo1()
	StructDemo3()
	StructDemo4()
}

// 定义一个结构体.首字母大写才能被其它包访问
type User struct {
	Name  string
	Age   int
	hobby []string
	//_     string //空标识符,不能被外部包和结构体所在的包使用
}

// 可以使用其他结构体作为自定义结构体中字段的类型，一般用于两个有关联的结构体中
type Order struct {
	orderId  string
	userData User //指向上面定义的User结构体
	User          //也可以不提供字段的名字，只使用类型
}

func StructDemo2() {
	//访问二级结构体的字段
	var order Order //零值初始化
	fmt.Println(order.userData.Name)
	fmt.Println(order.User.Name)
	fmt.Println(order.Name) //可以省略掉二级结构体的名称,直接这样访问二级结构体的字段
}

func StructDemo3() {
	//结构体变量初始化
	//按顺序依次给每个结构体字段进行赋值(不推荐)
	var user1 = User{"zhangsan", 18, []string{"爬山", "跑步"}}
	//指定字段名赋值(推荐)
	var user2 = User{Name: "lisi", Age: 20, hobby: []string{"上网", "打球"}}
	user2.Age = 99            //修改属性值
	fmt.Println(user1, user2) //{zhangsan 18 [爬山 跑步]} {lisi 99 [上网 打球]}
	//初始化空字段
	user3 := User{}
	fmt.Println(user3) //{ 0 []}
	//使用 new 关键词,返回的是引用指针 (尽量少用)
	user4 := new(User) //等价于 user4 := &User{}
	fmt.Println(user4) //&{ 0 []}
	//赋值
	user4.Name = "rxbook"
	user4.Age = 33
	fmt.Println(user4)  //&{rxbook 33 []}
	fmt.Println(*user4) //{rxbook 33 []}
	//输出四种实例化的类型,获取指针就在前面加上&,获取指针对应的值就在前面加上*
	fmt.Printf("user1:%T, user2:%T, &user3:%T, user4:%T", user1, user2, &user3, user4) //user1:main.User, user2:main.User, user3:*main.User, user4:*main.User

	println()

	//使用标准库 unsafe 包提供的函数，获得结构体类型变量占用的内存大小，以及它每个字段在内存中相对于结构体变量起始地址的偏移量
	fmt.Println(unsafe.Sizeof(user2))        // 48 //结构体类型变量占用的内存大小
	fmt.Println(unsafe.Offsetof(user2.Name)) // 0 //字段Name在内存中相对于变量 user2 起始地址的偏移量
}

// Go 语言中结构体类型的大小受内存对齐约束的影响
// 比如下面两个结构体类型表示的抽象是相同的，但正是因为字段排列顺序不同，导致它们的大小也不同
func StructDemo4() {
	type T struct {
		b byte
		i int64
		u uint16
	}
	type S struct {
		b byte
		u uint16
		i int64
	}
	var t T
	fmt.Println(unsafe.Sizeof(t)) // 24
	var s S
	fmt.Println(unsafe.Sizeof(s)) // 16
}

// 定义一个空结构体
type Empty struct{}

func StructDemo1() {
	var s1 User
	var s2 Empty
	fmt.Println("s1:", unsafe.Sizeof(s1)) // 64
	fmt.Println("s2:", unsafe.Sizeof(s2)) // 空结构体类型变量的内存占用为 0

	//基于空结构体类型内存零开销的特性，经常以空结构体为元素类建立 channel，是目前能实现的内存占用最小的 Goroutine 间通信方式。
	// var c = make(chan Empty) //声明一个元素类型为Empty的channel
	// c <- Empty{}             //向channel写入一个"事件"
}

// 不可以在结构体中包含类型为自身的字段，也不可以递归使用
/*
type T struct {
	t T
}

//报错:invalid recursive type: T refers to itself

type T1 struct {
	t2 T2
}
type T2 struct {
	t1 T1
}

//报错: invalid recursive type T1
*/

// 但是却可以拥有自身类型的指针类型、以自身类型为元素类型的切片类型、以及以自身类型作为 value 类型的 map 类型的字段
type TA struct {
	t  *TA           // ok
	st []TA          // ok
	m  map[string]TA // ok
}

// 自引用只能使用指针
// 结构体内部引用自己，只能使用指针。或者说，在整个引用链上，如果构成循环那就只能用指针
type Node struct {
	//left Node
	//right Node

	left  *Node
	right *Node

	// 这个也会报错
	// nn NodeNode
}
type NodeNode struct {
	node Node
}
